package com.juma.cartridge.rlock.fair;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.juma.cartridge.rlock.LockNode;
import com.juma.cartridge.rlock.RLock;
import com.juma.cartridge.rlock.RedisLockManager;
import com.juma.cartridge.rlock.exception.LockException;

/**
 * 基于Redis队例的公平锁实现
 * @ClassName FairLock
 * @Description
 * @Author zhuangguangyou
 * @Date 2019年8月1日 上午10:34:05
 * @Version 1.0
 *
 */
public class FairLock extends RLock {

	private LockQueue lockQueue;
	
	private static final Logger log = LoggerFactory.getLogger(FairLock.class);
	
	public FairLock(RedisLockManager manager,LockNode node,Thread holder,LockQueue lq) {
		super(manager,node,holder);
		this.lockQueue = lq;
	}
	

	@Override
	protected final void acquireLock(long retryWaitTimes) {
		while(true) {
			LockNode head = lockQueue.getHead();
			if (head==null) {
				throw new LockException("获取锁失败::redis队列中没有当前事务的排队信息");
			}
			
			if(!getId().equals(head.getLockId())) {
				if(head.getExpireTime() <= System.currentTimeMillis()) {
					lockQueue.removeNode(head);
					log.debug("检测到遗留的锁[id={}]已过期,已删除!",head.getLockId());
				} else {
					if(retryWaitTimes>=10) {
						try {
							Thread.sleep(retryWaitTimes);
						}catch(Exception e){}	
					}
				}
				continue;
			}
			//获得锁
			node.setExpireTime(head.getExpireTime());
			break;
		}
	}

	@Override
	protected final void releaseLock() {
		List<LockNode> nodes = lockQueue.getNodes(0, 1);
		LockNode head = nodes.get(0);
		if(head!=null && getId().equals(head.getLockId())) {
			//先将下一个锁的expireTime重新计算
			LockNode next = null;
			if(nodes.size()==2) {
				next = nodes.get(1);
				next.setExpireTime(System.currentTimeMillis()+next.getLockSeconds()*1000);	
			}
			lockQueue.moveHead(head,next);
		}
	}
	
	/**
	 * 锁续租
	 * @param lock
	 * @param seconds
	 * @return
	 */
	@Override
	public final void lease() {
		LockNode head = lockQueue.getHead();
		
		if(head!=null && getId().equals(head.getLockId())) {
			long expireTime = System.currentTimeMillis() + getLockSeconds() * 1000;
			head.setExpireTime(expireTime);
			lockQueue.setNode(0, head);
			node.setExpireTime(expireTime);
		}
	}
	
}
